package com.aluka.nirvana.framework.security.handler;

import cn.hutool.core.exceptions.ExceptionUtil;
import com.aluka.nirvana.framework.security.constant.Constants;
import com.aluka.nirvana.framework.security.principal.BaseUserDetails;
import com.aluka.nirvana.framework.security.provider.AuthenticationValidProvider;
import com.aluka.nirvana.framework.security.utils.JwtUtils;
import com.aluka.nirvana.framework.security.utils.ResponseUtils;
import com.google.common.base.Throwables;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.AntPathMatcher;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

/**
 * 自定义 Security Basic Filter 主要增加 Token的验证
 * @author gongli
 * @since 2019/4/10 13:40
 */
@Slf4j
public class CustomTokenAuthenticationHandler extends BasicAuthenticationFilter {

    @Setter
    private String[] openResources;
    @Setter
    private AuthenticationValidProvider authenticationValidProvider;
    @Setter
    private boolean authenticationValidEnabled;

    public CustomTokenAuthenticationHandler(AuthenticationManager authenticationManager){
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 判断当前请求是否拦截
        if(intercept(request)) {
            String tokenHeader = request.getHeader(HttpHeaders.AUTHORIZATION);
            if (StringUtils.isEmpty(tokenHeader) || !tokenHeader.startsWith(Constants.TOKEN_PREFIX)) {
                ResponseUtils.writeResponse(response, HttpStatus.FORBIDDEN, "Token 不存在!");
            }else{
                try {
                    // 获取请求头中去除前缀后的Json Web Token
                    String token = tokenHeader.replace(Constants.TOKEN_PREFIX, "");
                    // 解析Json Web Token 生成用户声明对象
                    Claims claims = Jwts.parser()
                            .setSigningKey(Constants.SIGN_SECRET)
                            .parseClaimsJws(token)
                            .getBody();
                    BaseUserDetails userDetails = parseClaims(claims);
                    // 进行权限过滤
                    if (handlerRequest(request, userDetails)) {
                        UsernamePasswordAuthenticationToken authentication =
                                new UsernamePasswordAuthenticationToken(userDetails, userDetails.getUsername(), userDetails.getAuthorities());
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                        chain.doFilter(request, response);// token认证通过 放行
                    } else {
                        ResponseUtils.writeResponse(response, HttpStatus.UNAUTHORIZED, "暂无权限访问此功能!");
                    }
                } catch (ExpiredJwtException e) {
                    ResponseUtils.writeResponse(response, HttpStatus.FORBIDDEN, "Token 已过期!");
                    log.warn("Token 已过期!");
                } catch (Exception e) {
                    ResponseUtils.writeResponse(response, HttpStatus.FORBIDDEN, "Token 无效!");
                    log.warn("Token 解析失败 >> {}", ExceptionUtil.stacktraceToString(Throwables.getRootCause(e), 255));
                }
            }
        }else{
            chain.doFilter(request, response);// token认证通过 放行
        }
    }

    private boolean handlerRequest(HttpServletRequest request, BaseUserDetails userDetails) {
        return userDetails.isAdmin()
                || !authenticationValidEnabled
                || isOpen(request.getRequestURI())
                || authenticationValidProvider == null
                || authenticationValidProvider.valid(userDetails, request.getRequestURI());
    }

    private boolean isOpen(String url){
        AntPathMatcher pathMatcher = new AntPathMatcher();
        for (String openResource : openResources) {
            if(pathMatcher.match(openResource,url)){
                return true;
            }
        }
        return false;
    }

    private boolean intercept(HttpServletRequest request){


        return !request.getMethod().equalsIgnoreCase(HttpMethod.OPTIONS.name())// 不拦截options请求
                && !isOpen(request.getRequestURI());// 不拦截开放的路径
    }

    private BaseUserDetails parseClaims(Claims claims){
        // 获取用户名
        String username = claims.getSubject();
        // 获取用户编号
        String userId = claims.getId();
        // 获取角色
        Set<GrantedAuthority> authorities = new HashSet<>();
        String authorityIdentify =
                claims.get(JwtUtils.AUTHORITIES) == null ? "" : claims.get(JwtUtils.AUTHORITIES).toString();
        for (String authority : StringUtils.split(authorityIdentify, ",")) {
            authorities.add(new SimpleGrantedAuthority(authority));
        }
        //组装参数
        BaseUserDetails userEntity = new BaseUserDetails();
        userEntity.setUsername(username);
        userEntity.setAuthorities(authorities);
        userEntity.setUserId(userId);
        userEntity.setAdmin(Boolean.valueOf(claims.getAudience()));
        return userEntity;
    }

}
